/**
* Jenga Mortgage Calculator - Frontend JavaScript
*
* @package Jenga_Mortgage_Calculator
*/
(function($) {
'use strict';
// Store calculation results for lead capture
var calculationResults = {};
var charts = {};
/**
* Format number as currency
*/
function formatCurrency(amount, currencyCode) {
var currencies = jmcData.currencies;
var symbol = currencies[currencyCode] ? currencies[currencyCode].symbol : currencyCode;
return symbol + numberWithCommas(parseFloat(amount).toFixed(2));
}
/**
* Add commas to number
*/
function numberWithCommas(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
/**
* Parse currency input (remove formatting)
*/
function parseCurrencyInput(value) {
if (!value) return 0;
return parseFloat(value.toString().replace(/[^0-9.-]/g, '')) || 0;
}
/**
* Format input as currency while typing
*/
function formatCurrencyInput(input) {
var value = input.val().replace(/[^0-9.]/g, '');
if (value) {
// Handle decimal places
var parts = value.split('.');
parts[0] = numberWithCommas(parts[0]);
if (parts[1] !== undefined) {
parts[1] = parts[1].substring(0, 2);
}
value = parts.join('.');
}
input.val(value);
}
/**
* Update currency symbols when currency changes
*/
function updateCurrencySymbols(calculator, currencyCode) {
if (jmcData.currencies && jmcData.currencies[currencyCode]) {
var symbol = jmcData.currencies[currencyCode].symbol;
calculator.find('.jmc-currency-symbol').text(symbol);
}
}
/**
* Get current currency from calculator
*/
function getCurrentCurrency(calculator) {
var currencySelect = calculator.find('select[name="currency"]');
if (currencySelect.length) {
return currencySelect.val();
}
// If no select, get from hidden field or use base currency
var hiddenCurrency = calculator.find('input[name="currency"]');
if (hiddenCurrency.length) {
return hiddenCurrency.val();
}
return jmcData.baseCurrency || 'NGN';
}
/**
* Show loading state on button
*/
function showLoading(btn) {
btn.prop('disabled', true);
btn.find('.jmc-btn-text').hide();
btn.find('.jmc-btn-loading').css('display', 'inline-flex');
}
/**
* Hide loading state on button
*/
function hideLoading(btn) {
btn.prop('disabled', false);
btn.find('.jmc-btn-text').show();
btn.find('.jmc-btn-loading').hide();
}
/**
* Show toast notification
*/
function showToast(message, type) {
type = type || 'info'; // 'success', 'error', 'info', 'warning'
// Remove any existing toasts
$('.jmc-toast').remove();
// Create toast element
var iconMap = {
success: '✓',
error: '✕',
warning: '⚠',
info: 'ℹ'
};
var toast = $('
');
// Add to body
$('body').append(toast);
// Trigger animation
setTimeout(function() {
toast.addClass('jmc-toast-show');
}, 10);
// Auto remove after 5 seconds
var autoRemove = setTimeout(function() {
removeToast(toast);
}, 5000);
// Manual close
toast.find('.jmc-toast-close').on('click', function() {
clearTimeout(autoRemove);
removeToast(toast);
});
}
/**
* Remove toast with animation
*/
function removeToast(toast) {
toast.removeClass('jmc-toast-show');
setTimeout(function() {
toast.remove();
}, 300);
}
/**
* Validate interest rate against minimum
*/
function validateInterestRate(input) {
var minRate = parseFloat(jmcData.minInterestRate) || 0;
var value = parseFloat(input.val()) || 0;
if (value < minRate) {
input.val(minRate);
return minRate;
}
return value;
}
/**
* Initialize Mortgage Calculator
*/
function initMortgageCalculator() {
var $form = $(document).find('#jmc-mortgage-form');
if (!$form.length) {
return;
}
var $calculator = $form.closest('.jmc-calculator');
// Currency input formatting
$calculator.on('input', '.jmc-currency-input', function() {
formatCurrencyInput($(this));
});
// Currency change
$calculator.on('change', '.jmc-currency-select', function() {
updateCurrencySymbols($calculator, $(this).val());
});
// Interest rate validation
$calculator.on('change', 'input[name="interest_rate"]', function() {
validateInterestRate($(this));
});
// Reset button handler
$calculator.on('click', '.jmc-reset-btn', function() {
$('#jmc-mortgage-results').removeClass('has-results');
});
// Form submission - use delegation
$(document).on('submit', '#jmc-mortgage-form', function(e) {
e.preventDefault();
e.stopPropagation();
var $thisForm = $(this);
var $btn = $thisForm.find('.jmc-calculate-btn');
var $calc = $thisForm.closest('.jmc-calculator');
showLoading($btn);
var currency = getCurrentCurrency($calc);
var data = {
action: 'jmc_calculate_mortgage',
nonce: jmcData.nonce,
loan_amount: parseCurrencyInput($thisForm.find('input[name="loan_amount"]').val()),
interest_rate: validateInterestRate($thisForm.find('input[name="interest_rate"]')),
loan_term: parseInt($thisForm.find('input[name="loan_term"]').val()) || 20,
currency: currency
};
$.ajax({
url: jmcData.ajaxUrl,
type: 'POST',
data: data,
success: function(response) {
hideLoading($btn);
if (response.success) {
displayMortgageResults(response.data);
calculationResults.mortgage = response.data;
// Store for lead capture
$('#jmc-mortgage-calculation-data').val(JSON.stringify(response.data));
} else {
showToast(response.data || jmcData.i18n.error, 'error');
}
},
error: function(xhr, status, error) {
hideLoading($btn);
console.error('AJAX Error:', error);
showToast(jmcData.i18n.error, 'error');
}
});
return false;
});
// Reset form
$(document).on('reset', '#jmc-mortgage-form', function() {
setTimeout(function() {
$('#jmc-mortgage-results').hide();
if (charts.mortgagePie) {
charts.mortgagePie.destroy();
charts.mortgagePie = null;
}
}, 10);
});
// Lead form submission
$(document).on('submit', '#jmc-mortgage-lead-form', function(e) {
e.preventDefault();
submitLead($(this), 'mortgage');
return false;
});
}
/**
* Display mortgage calculation results
*/
function displayMortgageResults(data) {
const results = $('#jmc-mortgage-results');
const currency = data.currency;
$('#jmc-mortgage-monthly-payment').text(formatCurrency(data.monthly_payment, currency));
$('#jmc-mortgage-total-payment').text(formatCurrency(data.total_payment, currency));
$('#jmc-mortgage-total-interest').text(formatCurrency(data.total_interest, currency));
$('#jmc-mortgage-loan-amount').text(formatCurrency(data.loan_amount, currency));
// Add has-results class to show content and hide empty state
results.addClass('has-results');
// Scroll to results on mobile (stacked layout)
if ($(window).width() < 992) {
$('html, body').animate({
scrollTop: results.offset().top - 20
}, 500);
}
// Create pie chart
createMortgagePieChart(data);
}
/**
* Create mortgage pie chart
*/
function createMortgagePieChart(data) {
const ctx = document.getElementById('jmc-mortgage-chart');
if (!ctx) return;
if (charts.mortgagePie) {
charts.mortgagePie.destroy();
}
const primaryColor = getComputedStyle(document.documentElement).getPropertyValue('--jmc-primary').trim() || '#1e3a5f';
const accentColor = getComputedStyle(document.documentElement).getPropertyValue('--jmc-accent').trim() || '#e74c3c';
charts.mortgagePie = new Chart(ctx, {
type: 'doughnut',
data: {
labels: [jmcData.i18n.principal, jmcData.i18n.interest],
datasets: [{
data: [data.loan_amount, data.total_interest],
backgroundColor: [primaryColor, accentColor],
borderWidth: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: true,
plugins: {
legend: {
position: 'bottom',
labels: {
padding: 20,
usePointStyle: true
}
},
tooltip: {
callbacks: {
label: function(context) {
return context.label + ': ' + formatCurrency(context.raw, data.currency);
}
}
}
}
}
});
}
/**
* Nigerian mortgage constants
*/
var NIGERIAN_RETIREMENT_AGE = 60;
var MAX_LOAN_TERM = 25;
var MAX_DTI = 33.3;
/**
* Calculate maximum loan term based on age
*/
function calculateMaxTerm(age) {
var yearsToRetirement = NIGERIAN_RETIREMENT_AGE - age;
return Math.min(Math.max(yearsToRetirement, 1), MAX_LOAN_TERM);
}
/**
* Update loan term based on age
*/
function updateLoanTermFromAge($form) {
var age = parseInt($form.find('input[name="age"]').val()) || 35;
var maxTerm = calculateMaxTerm(age);
var $termInput = $form.find('input[name="loan_term"]');
var currentTerm = parseInt($termInput.val()) || 20;
// Update max attribute
$termInput.attr('max', maxTerm);
// If current term exceeds max, adjust it
if (currentTerm > maxTerm) {
$termInput.val(maxTerm);
}
// Update hint text
var $hint = $form.find('.jmc-max-term-hint');
if ($hint.length) {
$hint.text('Max: ' + maxTerm + ' years (retirement at ' + NIGERIAN_RETIREMENT_AGE + ')');
}
return maxTerm;
}
/**
* Initialize Affordability Calculator
*/
function initAffordabilityCalculator() {
var $form = $(document).find('#jmc-affordability-form');
if (!$form.length) return;
var $calculator = $form.closest('.jmc-calculator');
// Initialize max term based on default age
updateLoanTermFromAge($form);
// Currency input formatting
$calculator.on('input', '.jmc-currency-input', function() {
formatCurrencyInput($(this));
});
// Currency change
$calculator.on('change', '.jmc-currency-select', function() {
updateCurrencySymbols($calculator, $(this).val());
});
// Age change - update max loan term
$calculator.on('change input', 'input[name="age"]', function() {
updateLoanTermFromAge($form);
});
// Interest rate validation
$calculator.on('change', 'input[name="interest_rate"]', function() {
validateInterestRate($(this));
});
// Reset button handler
$calculator.on('click', '.jmc-reset-btn', function() {
$('#jmc-affordability-results').removeClass('has-results');
// Reset max term to default based on default age
setTimeout(function() {
updateLoanTermFromAge($form);
}, 10);
});
// Form submission
$(document).on('submit', '#jmc-affordability-form', function(e) {
e.preventDefault();
e.stopPropagation();
var $thisForm = $(this);
var $btn = $thisForm.find('.jmc-calculate-btn');
var $calc = $thisForm.closest('.jmc-calculator');
showLoading($btn);
var currency = getCurrentCurrency($calc);
var age = parseInt($thisForm.find('input[name="age"]').val()) || 35;
var maxTerm = calculateMaxTerm(age);
var requestedTerm = parseInt($thisForm.find('input[name="loan_term"]').val()) || 20;
var actualTerm = Math.min(requestedTerm, maxTerm);
var data = {
action: 'jmc_calculate_affordability',
nonce: jmcData.nonce,
annual_income: parseCurrencyInput($thisForm.find('input[name="annual_income"]').val()),
monthly_debts: parseCurrencyInput($thisForm.find('input[name="monthly_debts"]').val()),
down_payment: parseCurrencyInput($thisForm.find('input[name="down_payment"]').val()),
interest_rate: validateInterestRate($thisForm.find('input[name="interest_rate"]')),
loan_term: actualTerm,
age: age,
currency: currency
};
$.ajax({
url: jmcData.ajaxUrl,
type: 'POST',
data: data,
success: function(response) {
hideLoading($btn);
if (response.success) {
displayAffordabilityResults(response.data, maxTerm, requestedTerm);
calculationResults.affordability = response.data;
// Store for lead capture
$('#jmc-affordability-calculation-data').val(JSON.stringify(response.data));
} else {
showToast(response.data || jmcData.i18n.error, 'error');
}
},
error: function() {
hideLoading($btn);
showToast(jmcData.i18n.error, 'error');
}
});
return false;
});
// Reset form
$(document).on('reset', '#jmc-affordability-form', function() {
setTimeout(function() {
$('#jmc-affordability-results').hide();
$('#jmc-afford-term-info').hide();
updateLoanTermFromAge($form);
}, 10);
});
// Lead form submission
$(document).on('submit', '#jmc-affordability-lead-form', function(e) {
e.preventDefault();
submitLead($(this), 'affordability');
return false;
});
}
/**
* Display affordability calculation results
*/
function displayAffordabilityResults(data, maxTerm, requestedTerm) {
const results = $('#jmc-affordability-results');
const currency = data.currency;
$('#jmc-afford-max-price').text(formatCurrency(data.max_home_price, currency));
$('#jmc-afford-max-loan').text(formatCurrency(data.max_loan_amount, currency));
$('#jmc-afford-down-payment').text(formatCurrency(data.down_payment, currency));
$('#jmc-afford-monthly').text(formatCurrency(data.max_monthly_payment, currency));
// Single DTI bar (Nigerian standard - 33.3%)
var dti = data.dti || data.back_end_dti || 0;
$('#jmc-afford-dti').text(dti.toFixed(1) + '%');
var dtiBar = $('#jmc-afford-dti-bar');
// Scale bar: 0-50% range mapped to 0-100% width
dtiBar.css('width', Math.min(dti, 50) * 2 + '%');
// Add warning/danger classes based on Nigerian 33.3% threshold
dtiBar.removeClass('warning danger good');
if (dti <= MAX_DTI) {
dtiBar.addClass('good');
} else if (dti <= 40) {
dtiBar.addClass('warning');
} else {
dtiBar.addClass('danger');
}
// Show term adjustment info if term was capped
var $termInfo = $('#jmc-afford-term-info');
if (maxTerm && requestedTerm && requestedTerm > maxTerm) {
$('#jmc-afford-term-message').text(
'Your loan term has been adjusted from ' + requestedTerm + ' to ' + maxTerm +
' years based on retirement age (' + NIGERIAN_RETIREMENT_AGE + ').'
);
$termInfo.show();
} else {
$termInfo.hide();
}
// Add has-results class to show content and hide empty state
results.addClass('has-results');
// Scroll to results on mobile (stacked layout)
if ($(window).width() < 992) {
$('html, body').animate({
scrollTop: results.offset().top - 20
}, 500);
}
}
/**
* Initialize Amortization Calculator
*/
function initAmortizationCalculator() {
var $form = $(document).find('#jmc-amortization-form');
if (!$form.length) return;
var $calculator = $form.closest('.jmc-calculator');
// Currency input formatting
$calculator.on('input', '.jmc-currency-input', function() {
formatCurrencyInput($(this));
});
// Currency change
$calculator.on('change', '.jmc-currency-select', function() {
updateCurrencySymbols($calculator, $(this).val());
});
// Interest rate validation
$calculator.on('change', 'input[name="interest_rate"]', function() {
validateInterestRate($(this));
});
// Reset button handler
$calculator.on('click', '.jmc-reset-btn', function() {
$('#jmc-amortization-results').removeClass('has-results');
});
// Form submission
$(document).on('submit', '#jmc-amortization-form', function(e) {
e.preventDefault();
e.stopPropagation();
var $thisForm = $(this);
var $btn = $thisForm.find('.jmc-calculate-btn');
var $calc = $thisForm.closest('.jmc-calculator');
showLoading($btn);
var currency = getCurrentCurrency($calc);
var data = {
action: 'jmc_calculate_amortization',
nonce: jmcData.nonce,
loan_amount: parseCurrencyInput($thisForm.find('input[name="loan_amount"]').val()),
interest_rate: validateInterestRate($thisForm.find('input[name="interest_rate"]')),
loan_term: parseInt($thisForm.find('input[name="loan_term"]').val()) || 20,
currency: currency
};
$.ajax({
url: jmcData.ajaxUrl,
type: 'POST',
data: data,
success: function(response) {
hideLoading($btn);
if (response.success) {
displayAmortizationResults(response.data);
calculationResults.amortization = response.data;
// Store for lead capture (without full schedule to keep email size reasonable)
var leadData = {
monthly_payment: response.data.monthly_payment,
total_payment: response.data.total_payment,
total_interest: response.data.total_interest,
total_principal: response.data.total_principal,
currency: response.data.currency
};
$('#jmc-amortization-calculation-data').val(JSON.stringify(leadData));
} else {
showToast(response.data || jmcData.i18n.error, 'error');
}
},
error: function() {
hideLoading($btn);
showToast(jmcData.i18n.error, 'error');
}
});
return false;
});
// Chart tabs
$(document).on('click', '.jmc-amortization-calculator .jmc-chart-tab', function() {
var chart = $(this).data('chart');
var $calc = $(this).closest('.jmc-calculator');
$calc.find('.jmc-chart-tab').removeClass('active');
$(this).addClass('active');
$calc.find('[id^="jmc-amort-chart-"]').hide();
$('#jmc-amort-chart-' + chart).show();
});
// Schedule view toggle
$(document).on('click', '.jmc-amortization-calculator .jmc-schedule-view-btn', function() {
var view = $(this).data('view');
var $calc = $(this).closest('.jmc-calculator');
$calc.find('.jmc-schedule-view-btn').removeClass('active');
$(this).addClass('active');
if (view === 'yearly') {
$('#jmc-amort-schedule-yearly').show();
$('#jmc-amort-schedule-monthly').hide();
} else {
$('#jmc-amort-schedule-yearly').hide();
$('#jmc-amort-schedule-monthly').show();
}
});
// Reset form
$(document).on('reset', '#jmc-amortization-form', function() {
setTimeout(function() {
$('#jmc-amortization-results').hide();
destroyAmortizationCharts();
}, 10);
});
// Lead form submission
$(document).on('submit', '#jmc-amortization-lead-form', function(e) {
e.preventDefault();
submitLead($(this), 'amortization');
return false;
});
}
/**
* Display amortization results
*/
function displayAmortizationResults(data) {
const results = $('#jmc-amortization-results');
const currency = data.currency;
$('#jmc-amort-monthly').text(formatCurrency(data.monthly_payment, currency));
$('#jmc-amort-total').text(formatCurrency(data.total_payment, currency));
$('#jmc-amort-interest').text(formatCurrency(data.total_interest, currency));
$('#jmc-amort-principal').text(formatCurrency(data.total_principal, currency));
// Populate tables
populateAmortizationTables(data, currency);
// Create charts
createAmortizationCharts(data, currency);
// Add has-results class to show content and hide empty state
results.addClass('has-results');
// Scroll to results on mobile (stacked layout)
if ($(window).width() < 992) {
$('html, body').animate({
scrollTop: results.offset().top - 20
}, 500);
}
}
/**
* Populate amortization tables
*/
function populateAmortizationTables(data, currency) {
// Yearly table
const yearlyTbody = $('#jmc-amort-yearly-tbody');
yearlyTbody.empty();
data.yearly_summary.forEach(function(row) {
yearlyTbody.append(`
${row.year}
${formatCurrency(row.principal, currency)}
${formatCurrency(row.interest, currency)}
${formatCurrency(row.balance, currency)}
`);
});
// Monthly table (limited to first 5 years for performance)
const monthlyTbody = $('#jmc-amort-monthly-tbody');
monthlyTbody.empty();
const scheduleToShow = data.schedule.slice(0, 60); // First 5 years
scheduleToShow.forEach(function(row) {
monthlyTbody.append(`
${row.month}
${formatCurrency(row.payment, currency)}
${formatCurrency(row.principal, currency)}
${formatCurrency(row.interest, currency)}
${formatCurrency(row.balance, currency)}
`);
});
if (data.schedule.length > 60) {
monthlyTbody.append(`